# coding:utf-8
# Author : hiicy redldw
# Date : 2019/03/20

from numpy import *
from matplotlib import pyplot as plt


def loadSimpData():
    datMat = matrix([[1., 2.1],
                     [2., 1.1],
                     [1.3, 1.],
                     [1., 1.],
                     [2., 1.]])
    classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
    # d = datMat.A
    # o = d[array(classLabels)==1]
    # t = d[array(classLabels)!=1]
    # plt.figure()
    # plt.scatter(o[:,0],o[:,1],color='r')
    # plt.scatter(t[:,0],t[:,1],color='g')
    # plt.show()
    return datMat, classLabels


# 测试是否有某个值小于或者大于我们正在测试的阈值
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
    retArray = ones((shape(dataMatrix)[0], 1))
    if threshIneq == "lt":
        retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
    else:
        retArray[dataMatrix[:, dimen] > threshVal] = -1.0
    return retArray


# 会在一个加权数据集中循环，并找到具有最低错误率的单层决策树 ~= 弱分类器
def buildStump(dataArr, classLabels, D):
    dataMatrix = mat(dataArr)
    labelMat = mat(classLabels).T
    m, n = shape(dataMatrix)
    numSteps = 10.0
    bestStump = {}
    bestClassEst = mat(zeros((m, 1)))
    minError = inf
    for i in range(n):  # 针对每个特征
        rangeMin = dataMatrix[:, i].min();
        rangeMax = dataMatrix[:, i].max()
        stepSize = (rangeMax - rangeMin) / numSteps
        for j in range(-1, int(numSteps) + 1):
            for ineuqal in ['lt', 'gt']:

                threshVal = (rangeMin + float(j) * stepSize)
                predictedVals = stumpClassify(dataMatrix, i, threshVal, ineuqal)

                errArr = mat(ones((m, 1)))
                errArr[predictedVals == labelMat] = 0
                # FAQ:计算加权错误率,为何不是普遍认知的错误率
                weightedError = D.T * errArr

                if weightedError < minError:
                    minError = weightedError
                    bestClassEst = predictedVals.copy()
                    bestStump['dim'] = i
                    bestStump['thresh'] = threshVal
                    bestStump['ineq'] = ineuqal
    return bestStump, minError, bestClassEst

# adaBoost 训练
def adaBoostTrainDS(dataArr, classLabels, numIt=40):
    weakClassArr = []  # 弱分类器数组
    m = shape(dataArr)[0]
    D = mat(ones((m, 1)) / m)
    aggClassEst = mat(zeros((m, 1)))  # 记录每个数据点的类别估计累计值
    for i in range(numIt):
        bestStump, error, classEst = buildStump(dataArr, classLabels, D)
        print(f'D: {D.T}')
        alpha = float(0.5 * log((1 - error) / max(error, 1e-16)))
        bestStump['alpha'] = alpha  # 存储该分类器决定系数
        weakClassArr.append(bestStump)
        print(f'classEst: {classEst.T}')
        # REW:TRICK:样本更改权重的标记 正确分类、错误分类样本都同时更改了
        expon = multiply(-1 * alpha * mat(classLabels).T, classEst)
        D = multiply(D, exp(expon))
        D = D / D.sum()
        print('alpha: ', alpha)
        aggClassEst += alpha * classEst
        print(f'aggClassEst: {aggClassEst.T}')
        # 根据符号找出不等 求出错误率
        aggErrors = multiply(sign(aggClassEst) !=
                             mat(classLabels).T, ones((m, 1)))  # ~= j[~(f!=m)]=0
        errorRate = aggErrors.sum() / m
        print(f'total Error: ', errorRate, '\n')
        if errorRate == 0.0: break
    return weakClassArr


def adaClassify(datToClass, classifierArr):
    """
    :param datToClass:
    :param classifierArr: 弱分类器数组
    :return:
    """
    dataMatrix = mat(datToClass)
    m = shape(dataMatrix)[0]
    aggClassEst = mat(zeros((m, 1)))
    for i in range(len(classifierArr)):
        classEst = stumpClassify(dataMatrix, classifierArr[i]['dim'],
                                 classifierArr[i]['thresh'],
                                 classifierArr[i]['ineq'])
        aggClassEst += classifierArr[i]['alpha']*classEst
        print(aggClassEst)
    return sign(aggClassEst)


def loadDataSet(fileName):      #general function to parse tab -delimited floats
    numFeat = len(open(fileName).readline().split('\t')) #get number of fields
    dataMat = []; labelMat = []
    fr = open(fileName)
    for line in fr.readlines():
        lineArr =[]
        curLine = line.strip().split('\t')
        for i in range(numFeat-1):
            lineArr.append(float(curLine[i]))
        dataMat.append(lineArr)
        labelMat.append(float(curLine[-1]))
    return dataMat,labelMat

def plotROC(predStrengths,classLabels):
    import matplotlib.pyplot as plt
    cur = (1.0,1.0)
    ySum = 0.0
    numPosClas = sum(array(classLabels) == 1.0)
    yStep = 1/float(numPosClas) # FAQ:?假阳率那些怎么算的
    xStep = 1/float(len(classLabels) - numPosClas)
    sortedIndicies = predStrengths.argsort() # 获取排好序的索引
    fig = plt.figure()
    fig.clf()
    ax = plt.subplot(111)
    for index in sortedIndicies.tolist()[0]:
        if classLabels[index] == 1.0:
            delX = 0;delY = yStep
        else:
            delX = xStep;delY = 0
            ySum += cur[1]
        ax.plot([cur[0], cur[0] - delX], [cur[1], cur[1] - delY], c='b')
        cur = (cur[0]-delX,cur[1]-delY)
    ax.plot([0, 1], [0, 1], 'b--')
    plt.xlabel('False positive rate')
    plt.ylabel('True positive rate')
    plt.title('ROC curve for AdaBoost horse colic detection system')
    ax.axis([0, 1, 0, 1])
    plt.show()
    print("the Area Under the Curve is: ", ySum * xStep)


if __name__ == "__main__":
    dataMat, classLabels = loadSimpData()
    classifierArray = adaBoostTrainDS(dataMat, classLabels, 9)
    print(classifierArray)
